/*
 * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved. DO NOT ALTER
 * OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * This code is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2 only, as published by
 * the Free Software Foundation. Sun designates this particular file as subject
 * to the "Classpath" exception as provided by Sun in the LICENSE file that
 * accompanied this code.
 * 
 * This code is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License version 2 for more
 * details (a copy is included in the LICENSE file that accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version 2
 * along with this work; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, CA
 * 95054 USA or visit www.sun.com if you need additional information or have any
 * questions.
 */
package com.excilys.jnullsafe;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.Map;

import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl;

/**
 * This is utility class to resolve types.
 * 
 * @since 1.7
 * 
 * @author Eamonn McManus
 * @author Sergey Malenkov
 */
public final class TypeResolver {
    /**
     * Replaces the given {@code type} in an inherited method with the actual
     * type it has in the given {@code inClass}.
     * 
     * <p>
     * Although type parameters are not inherited by subclasses in the Java
     * language, they <em>are</em> effectively inherited when using reflection.
     * For example, if you declare an interface like this...
     * </p>
     * 
     * <pre>
     * public interface StringToIntMap extends Map&lt;String, Integer&gt; {}
     * </pre>
     * 
     * <p>
     * ...then StringToIntMap.class.getMethods() will show that it has methods
     * like put(K,V) even though StringToIntMap has no type parameters. The K
     * and V variables are the ones declared by Map, so
     * {@link TypeVariable#getGenericDeclaration()} will return Map.class.
     * </p>
     * 
     * <p>
     * The purpose of this method is to take a Type from a possibly-inherited
     * method and replace it with the correct Type for the inheriting class. So
     * given parameters of K and StringToIntMap.class in the above example, this
     * method will return String.
     * </p>
     * 
     * @param inClass
     *            the base class used to resolve
     * @param type
     *            the type to resolve
     * @return a resolved type
     * 
     * @see #getActualType(Class)
     * @see #resolve(Type,Type)
     */
    public static Type resolveInClass(Class<?> inClass, Type type) {
        return resolve(getActualType(inClass), type);
    }

    /**
     * Replaces all {@code types} in the given array with the actual types they
     * have in the given {@code inClass}.
     * 
     * @param inClass
     *            the base class used to resolve
     * @param types
     *            the array of types to resolve
     * @return an array of resolved types
     * 
     * @see #getActualType(Class)
     * @see #resolve(Type,Type[])
     */
    public static Type[] resolveInClass(Class<?> inClass, Type[] types) {
        return resolve(getActualType(inClass), types);
    }

    /**
     * Replaces type variables of the given {@code formal} type with the types
     * they stand for in the given {@code actual} type.
     * 
     * <p>
     * A ParameterizedType is a class with type parameters, and the values of
     * those parameters. For example, Map&lt;K,V> is a generic class, and a
     * corresponding ParameterizedType might look like
     * Map&lt;K=String,V=Integer>. Given such a ParameterizedType, this method
     * will replace K with String, or List&lt;K> with List&ltString;, or
     * List&lt;? super K> with List&lt;? super String>.
     * </p>
     * 
     * <p>
     * The {@code actual} argument to this method can also be a Class. In this
     * case, either it is equivalent to a ParameterizedType with no parameters
     * (for example, Integer.class), or it is equivalent to a "raw"
     * ParameterizedType (for example, Map.class). In the latter case, every
     * type parameter declared or inherited by the class is replaced by its
     * "erasure". For a type parameter declared as &lt;T>, the erasure is
     * Object. For a type parameter declared as &lt;T extends Number>, the
     * erasure is Number.
     * </p>
     * 
     * <p>
     * Although type parameters are not inherited by subclasses in the Java
     * language, they <em>are</em> effectively inherited when using reflection.
     * For example, if you declare an interface like this...
     * </p>
     * 
     * <pre>
     * public interface StringToIntMap extends Map&lt;String, Integer&gt; {}
     * </pre>
     * 
     * <p>
     * ...then StringToIntMap.class.getMethods() will show that it has methods
     * like put(K,V) even though StringToIntMap has no type parameters. The K
     * and V variables are the ones declared by Map, so
     * {@link TypeVariable#getGenericDeclaration()} will return {@link Map
     * Map.class}.
     * </p>
     * 
     * <p>
     * For this reason, this method replaces inherited type parameters too.
     * Therefore if this method is called with {@code actual} being
     * StringToIntMap.class and {@code formal} being the K from Map, it will
     * return {@link String String.class}.
     * </p>
     * 
     * <p>
     * In the case where {@code actual} is a "raw" ParameterizedType, the
     * inherited type parameters will also be replaced by their erasures. The
     * erasure of a Class is the Class itself, so a "raw" subinterface of
     * StringToIntMap will still show the K from Map as String.class. But in a
     * case like this...
     * 
     * <pre>
     * public interface StringToIntListMap extends Map&lt;String, List&lt;Integer&gt;&gt; {}
     * 
     * public interface RawStringToIntListMap extends StringToIntListMap {}
     * </pre>
     * 
     * <p>
     * ...the V inherited from Map will show up as List&lt;Integer> in
     * StringToIntListMap, but as plain List in RawStringToIntListMap.
     * </p>
     * 
     * @param actual
     *            the type that supplies bindings for type variables
     * @param formal
     *            the type where occurrences of the variables in {@code actual}
     *            will be replaced by the corresponding bound values
     * @return a resolved type
     * 
     * @see #TypeResolver(Type)
     * @see #resolve(Type)
     */
    public static Type resolve(Type actual, Type formal) {
        return new TypeResolver(actual).resolve(formal);
    }

    /**
     * Replaces type variables of all formal types in the given array with the
     * types they stand for in the given {@code actual} type.
     * 
     * @param actual
     *            the type that supplies bindings for type variables
     * @param formals
     *            the array of types to resolve
     * @return an array of resolved types
     * 
     * @see #TypeResolver(Type)
     * @see #resolve(Type[])
     */
    public static Type[] resolve(Type actual, Type[] formals) {
        return new TypeResolver(actual).resolve(formals);
    }

    /**
     * Converts the given {@code type} to the corresponding class. This method
     * implements the concept of type erasure, that is described in <a href="http://jscstage.sfbay.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.6"
     * >section 4.6</a> of Java Language Specification.
     * 
     * @param type
     *            the array of types to convert
     * @return a corresponding class
     */
    public static Class<?> erase(Type type) {
        if (type instanceof Class) {
            return (Class<?>) type;
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            return (Class<?>) pt.getRawType();
        }
        if (type instanceof TypeVariable) {
            TypeVariable tv = (TypeVariable) type;
            Type[] bounds = tv.getBounds();
            return 0 < bounds.length ? erase(bounds[0]) : Object.class;
        }
        if (type instanceof WildcardType) {
            WildcardType wt = (WildcardType) type;
            Type[] bounds = wt.getUpperBounds();
            return 0 < bounds.length ? erase(bounds[0]) : Object.class;
        }
        if (type instanceof GenericArrayType) {
            GenericArrayType gat = (GenericArrayType) type;
            return Array.newInstance(erase(gat.getGenericComponentType()), 0).getClass();
        }
        throw new IllegalArgumentException("Unknown Type kind: " + type.getClass());
    }

    /**
     * Converts all {@code types} in the given array to the corresponding
     * classes.
     * 
     * @param types
     *            the array of types to convert
     * @return an array of corresponding classes
     * 
     * @see #erase(Type)
     */
    public static Class[] erase(Type[] types) {
        int length = types.length;
        Class[] classes = new Class[length];
        for (int i = 0; i < length; i++) {
            classes[i] = TypeResolver.erase(types[i]);
        }
        return classes;
    }

    private final Map<TypeVariable<?>, Type> map = new HashMap<TypeVariable<?>, Type>();

    /**
     * Constructs the type resolver for the given actual type.
     * 
     * @param actual
     *            the type that supplies bindings for type variables
     * 
     * @see #prepare(Type)
     */
    private TypeResolver(Type actual) {
        prepare(actual);
    }

    /**
     * Fills the map from type parameters to types as seen by the given {@code
     * type}. The method is recursive because the {@code type} inherits mappings
     * from its parent classes and interfaces. The {@code type} can be either a
     * {@link Class Class} or a {@link ParameterizedType ParameterizedType}. If
     * it is a {@link Class Class}, it is either equivalent to a
     * {@link ParameterizedType ParameterizedType} with no parameters, or it
     * represents the erasure of a {@link ParameterizedType ParameterizedType}.
     * 
     * @param type
     *            the next type in the hierarchy
     */
    private void prepare(Type type) {
        Class<?> raw = (Class<?>) (type instanceof Class<?> ? type : ((ParameterizedType) type).getRawType());

        TypeVariable<?>[] formals = raw.getTypeParameters();

        Type[] actuals = type instanceof Class<?> ? formals : ((ParameterizedType) type).getActualTypeArguments();

        assert formals.length == actuals.length;
        for (int i = 0; i < formals.length; i++) {
            map.put(formals[i], actuals[i]);
        }
        Type gSuperclass = raw.getGenericSuperclass();
        if (gSuperclass != null) {
            prepare(gSuperclass);
        }
        for (Type gInterface : raw.getGenericInterfaces()) {
            prepare(gInterface);
        }
        // If type is the raw version of a parameterized class, we type-erase
        // all of its type variables, including inherited ones.
        if (type instanceof Class<?> && formals.length > 0) {
            for (Map.Entry<TypeVariable<?>, Type> entry : map.entrySet()) {
                entry.setValue(erase(entry.getValue()));
            }
        }
    }

    /**
     * Replaces the given {@code formal} type with the type it stand for in this
     * type resolver.
     * 
     * @param formal
     *            the array of types to resolve
     * @return a resolved type
     */
    private Type resolve(Type formal) {
        if (formal instanceof Class) {
            return formal;
        }
        if (formal instanceof GenericArrayType) {
            Type comp = ((GenericArrayType) formal).getGenericComponentType();
            comp = resolve(comp);
            return comp instanceof Class ? Array.newInstance((Class<?>) comp, 0).getClass() : GenericArrayTypeImpl
                    .make(comp);
        }
        if (formal instanceof ParameterizedType) {
            ParameterizedType fpt = (ParameterizedType) formal;
            Type[] actuals = resolve(fpt.getActualTypeArguments());
            return ParameterizedTypeImpl.make((Class<?>) fpt.getRawType(), actuals, fpt.getOwnerType());
        }
        if (formal instanceof WildcardType) {
            WildcardType fwt = (WildcardType) formal;
            Type[] upper = resolve(fwt.getUpperBounds());
            Type[] lower = resolve(fwt.getLowerBounds());
            return new WildcardTypeImpl(upper, lower);
        }
        if (!(formal instanceof TypeVariable)) {
            throw new IllegalArgumentException("Bad Type kind: " + formal.getClass());
        }
        Type actual = map.get(formal);
        if (actual == null || actual.equals(formal)) {
            return formal;
        }
        actual = fixGenericArray(actual);
        return resolve(actual);
        // A variable can be bound to another variable that is itself bound
        // to something. For example, given:
        // class Super<T> {...}
        // class Mid<X> extends Super<T> {...}
        // class Sub extends Mid<String>
        // the variable T is bound to X, which is in turn bound to String.
        // So if we have to resolve T, we need the tail recursion here.
    }

    /**
     * Replaces all formal types in the given array with the types they stand
     * for in this type resolver.
     * 
     * @param formals
     *            the array of types to resolve
     * @return an array of resolved types
     * 
     * @see #resolve(Type)
     */
    private Type[] resolve(Type[] formals) {
        int length = formals.length;
        Type[] actuals = new Type[length];
        for (int i = 0; i < length; i++) {
            actuals[i] = resolve(formals[i]);
        }
        return actuals;
    }

    /**
     * Replaces a {@link GenericArrayType GenericArrayType} with plain array
     * class where it is possible. Bug <a
     * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5041784"
     * >5041784</a> is that arrays of non-generic type sometimes show up as
     * {@link GenericArrayType GenericArrayType} when using reflection. For
     * example, a {@code String[]} might show up as a {@link GenericArrayType
     * GenericArrayType} where {@link GenericArrayType#getGenericComponentType
     * getGenericComponentType} is {@code String.class}. This violates the
     * specification, which says that {@link GenericArrayType GenericArrayType}
     * is used when the component type is a type variable or parameterized type.
     * We fit the specification here.
     * 
     * @param type
     *            the type to fix
     * @return a corresponding type for the generic array type, or the same type
     *         as {@code type}
     */
    private static Type fixGenericArray(Type type) {
        if (type instanceof GenericArrayType) {
            Type comp = ((GenericArrayType) type).getGenericComponentType();
            comp = fixGenericArray(comp);
            if (comp instanceof Class) {
                return Array.newInstance((Class<?>) comp, 0).getClass();
            }
        }
        return type;
    }

    /**
     * Replaces a {@link Class Class} with type parameters with a
     * {@link ParameterizedType ParameterizedType} where every parameter is
     * bound to itself. When calling {@link #resolveInClass} in the context of
     * {@code inClass}, we can't just pass {@code inClass} as the {@code actual}
     * parameter, because if {@code inClass} has type parameters that would be
     * interpreted as accessing the raw type, so we would get unwanted erasure.
     * This is why we bind each parameter to itself. If {@code inClass} does
     * have type parameters and has methods where those parameters appear in the
     * return type or argument types, we will correctly leave those types alone.
     * 
     * @param inClass
     *            the base class used to resolve
     * @return a parameterized type for the class, or the same class as {@code
     *         inClass}
     */
    private static Type getActualType(Class<?> inClass) {
        Type[] params = inClass.getTypeParameters();
        return params.length == 0 ? inClass : ParameterizedTypeImpl.make(inClass, params, inClass.getEnclosingClass());
    }
}
